package com.agent;

import lombok.extern.slf4j.Slf4j;
import org.objectweb.asm.*;
import org.objectweb.asm.commons.AdviceAdapter;

/**
 * 实现 ASM MethodVisitor
 */
@Slf4j
public class MonitoringMethodVisitor extends AdviceAdapter {
    private final String methodName;
    private final String className;
    private final Label startFinally = new Label();
    private int timeLocal;

    protected MonitoringMethodVisitor(int api, MethodVisitor mv,
                                      int access, String name,
                                      String desc, String className) {
        super(api, mv, access, name, desc);
        this.methodName = name;
        this.className = className;
    }

    @Override
    protected void onMethodEnter() {
        // 1. 记录开始时间
        mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
        timeLocal = newLocal(Type.LONG_TYPE);
        mv.visitVarInsn(LSTORE, timeLocal);

        // 2. 生成监控点ID (可选)
        mv.visitMethodInsn(INVOKESTATIC, "java/util/UUID", "randomUUID", "()Ljava/util/UUID;", false);
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/util/UUID", "toString", "()Ljava/lang/String;", false);
        int traceIdLocal = newLocal(Type.getType(String.class));
        mv.visitVarInsn(ASTORE, traceIdLocal);

        // 3. 调用监控器记录方法入口
        mv.visitLdcInsn(className.replace('/', '.'));
        mv.visitLdcInsn(methodName);
        mv.visitVarInsn(ALOAD, traceIdLocal);
        mv.visitMethodInsn(INVOKESTATIC, "com/agent/MethodMonitor", "methodEntry",
                "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", false);

        // 4. 设置异常处理块起点
        mv.visitLabel(startFinally);
    }

    /**
     * 处理正常返回
     * @param opcode
     */
    @Override
    protected void onMethodExit(int opcode) {
        if (opcode != ATHROW) {
            recordExitInfo(null);
        }
    }

    /**
     * 处理异常退出
     * @param maxStack
     * @param maxLocals
     */
    @Override
    public void visitMaxs(int maxStack, int maxLocals) {
        Label endFinally = new Label();
        mv.visitTryCatchBlock(startFinally, endFinally, endFinally, null);
        mv.visitLabel(endFinally);
        onFinally(ATHROW);
        mv.visitInsn(ATHROW);
        super.visitMaxs(maxStack, maxLocals);
    }

    private void onFinally(int opcode) {
        if (opcode == ATHROW) {
            // 将异常对象存入局部变量
            dup();
            int exceptionLocal = newLocal(Type.getType(Throwable.class));
            storeLocal(exceptionLocal, Type.getType(Throwable.class));
            recordExitInfo(exceptionLocal);
        }
    }

    private void recordExitInfo(Integer exceptionLocal) {
        // 1. 计算执行耗时
        mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
        mv.visitVarInsn(LLOAD, timeLocal);
        mv.visitInsn(LSUB);
        int durationLocal = newLocal(Type.LONG_TYPE);
        mv.visitVarInsn(LSTORE, durationLocal);

        // 2. 调用监控器记录方法退出
        mv.visitLdcInsn(className.replace('/', '.'));
        mv.visitLdcInsn(methodName);
        mv.visitVarInsn(LLOAD, durationLocal);

        if (exceptionLocal != null) {
            mv.visitVarInsn(ALOAD, exceptionLocal);
        } else {
            mv.visitInsn(ACONST_NULL);
        }

        mv.visitMethodInsn(INVOKESTATIC, "com/agent/MethodMonitor", "methodExit",
                "(Ljava/lang/String;Ljava/lang/String;JLjava/lang/Throwable;)V", false);
    }
}